import { runAsyncTask } from "../common/runAsynctask.js";

const PENDING = "pending";
const FULFILLED = "fulfilled";
const REJECTED = "rejected";

class MyPromise {
  state = PENDING;
  result = undefined;
  #handlers = []; // [{onFulfilled, onRejected}]
  constructor(executor) {
    try {
      executor(this.resolve.bind(this), this.reject.bind(this));
    } catch (err) {
      this.reject(err);
    }
  }

  resolve(result) {
    if (this.state === PENDING) {
      if (result instanceof MyPromise) {
        result.then(
          (v) => {
            this.state = FULFILLED;
            this.result = v;
            this.#handlers.forEach(({ onFulfilled }) => onFulfilled());
          },
          (r) => {
            this.state = REJECTED;
            this.result = r;
            this.#handlers.forEach(({ onRejected }) => onRejected());
          }
        );
      } else {
        this.state = FULFILLED;
        this.result = result;
        this.#handlers.forEach(({ onFulfilled }) => onFulfilled());
      }
    }
  }

  reject(result) {
    if (result instanceof MyPromise) {
      result.then(
        (v) => {
          this.state = FULFILLED;
          this.result = v;
          this.#handlers.forEach(({ onFulfilled }) => onFulfilled());
        },
        (r) => {
          this.state = REJECTED;
          this.result = r;
          this.#handlers.forEach(({ onRejected }) => onRejected());
        }
      );
    } else if (this.state === PENDING) {
      this.state = REJECTED;
      this.result = result;
      this.#handlers.forEach(({ onRejected }) => onRejected());
    }
  }

  then(onFulfilled, onRejected) {
    onFulfilled = typeof onFulfilled === "function" ? onFulfilled : (v) => v;
    onRejected =
      typeof onRejected === "function"
        ? onRejected
        : (e) => {
            throw e;
          };
    const p2 = new MyPromise((resolve, reject) => {
      if (this.state === FULFILLED) {
        runAsyncTask(() => {
          try {
            let x = onFulfilled(this.result);
            resolvePromise(p2, x, resolve, reject);
          } catch (err) {
            reject(err);
          }
        });
      } else if (this.state === REJECTED) {
        runAsyncTask(() => {
          try {
            let x = onRejected(this.result);
            resolvePromise(p2, x, resolve, reject);
          } catch (err) {
            reject(err);
          }
        });
      } else {
        this.#handlers.push({
          onFulfilled: () => {
            runAsyncTask(() => {
              try {
                let x = onFulfilled(this.result);
                resolvePromise(p2, x, resolve, reject);
              } catch (err) {
                reject(err);
              }
            });
          },
          onRejected: () => {
            runAsyncTask(() => {
              try {
                let x = onRejected(this.result);
                resolvePromise(p2, x, resolve, reject);
              } catch (err) {
                reject(err);
              }
            });
          },
        });
      }
    });

    return p2;
  }

  catch(onRejected) {
    return this.then(undefined, onRejected);
  }

  finally(callback) {
    return this.then(
      (v) => MyPromise.resolve(callback()).then(() => v),
      (r) =>
        MyPromise.reject(callback()).then(undefined, () => {
          throw r;
        })
    );
  }

  static resolve(value) {
    if (value instanceof MyPromise) {
      return value;
    }
    return new MyPromise((resolve) => {
      resolve(value);
    });
  }

  static reject(value) {
    if (value instanceof MyPromise) {
      return value;
    }
    return new MyPromise((resolve, reject) => {
      reject(value);
    });
  }

  static all(promiseArr) {
    let index = 0;
    const result = [];

    return new MyPromise((resolve, reject) => {
      promiseArr.forEach((p, i) => {
        MyPromise.resolve(p).then(
          (val) => {
            index++;
            result[i] = val;
            if (index === promiseArr.length) {
              resolve(result);
            }
          },
          (err) => {
            reject(err);
          }
        );
      });
    });
  }

  static race(promiseArr) {
    return new MyPromise((resolve, reject) => {
      promiseArr.forEach((p, i) => {
        MyPromise.resolve(p).then(
          (val) => {
            resolve(val);
          },
          (err) => {
            reject(err);
          }
        );
      });
    });
  }

  static allSettled(promiseArr) {
    let index = 0;
    const result = [];

    return new MyPromise((resolve, reject) => {
      promiseArr.forEach((p, i) => {
        MyPromise.resolve(p).then(
          (value) => {
            index++;
            result[i] = {
              status: "fulfilled",
              value,
            };
            if (index === promiseArr.length) {
              resolve(result);
            }
          },
          (reason) => {
            index++;
            result[i] = {
              status: "rejected",
              reason,
            };
            if (index === promiseArr.length) {
              resolve(result);
            }
          }
        );
      });
    });
  }

  static any(promiseArr) {
    let time = 0;
    const rejectReasons = [];
    return new MyPromise((resolve, reject) => {
      promiseArr.forEach((p, i) => {
        MyPromise.resolve(p)
          .then((value) => {
            resolve(value);
          })
          .catch((reason) => {
            time++;
            rejectReasons[i] = reason;
            if (time === promiseArr.length) {
              reject(
                new AggregateError(rejectReasons, "All promises were rejected")
              );
            }
          });
      });
    });
  }
}

function resolvePromise(p2, x, resolve, reject) {
  // 处理返回自己
  if (x === p2) {
    throw new TypeError("Chaining cycle detected for promise #<Promise>");
  }
  // 核心功能版本，不考虑边界情况
  // // 处理返回 MyPromise
  // if (x instanceof MyPromise) {
  //   x.then(resolve, reject);
  // } else {
  //   // 处理其他情况
  //   resolve(x);
  // }
  // 符合 promise A+规范的代码
  if (x instanceof MyPromise) {
    x.then((y) => {
      resolvePromise(p2, y, resolve, reject);
    }, reject);
  } else if (x !== null && (typeof x === "object" || typeof x === "function")) {
    try {
      let then = x.then;
    } catch (e) {
      return reject(e);
    }
    if (typeof then === "function") {
      let called = false;
      try {
        then.call(
          x,
          (y) => {
            if (called) return;
            called = true;
            resolvePromise(p2, y, resolve, reject);
          },
          (r) => {
            if (called) return;
            called = true;
            reject(r);
          }
        );
      } catch (e) {
        if (called) return;
        called = true;
        reject(e);
      }
    } else {
      resolve(x);
    }
  } else {
    resolve(x);
  }
}

MyPromise.deferred = function () {
  const result = {};
  result.promise = new MyPromise((resolve, reject) => {
    result.resolve = resolve;
    result.reject = reject;
  });
  return result;
};

export default MyPromise;
